package ar.pegasus.clasificador.util;

import ar.pegasus.clasificador.dao.api.AbreviaturaManager;
import ar.pegasus.clasificador.entidades.Seccion;
import ar.pegasus.clasificador.enums.entidades.EnumSeparadoresCalle;
import ar.pegasus.clasificador.excepciones.CalleSinAlturaException;
import ar.pegasus.framework.componentes.PException;

public abstract class NombreParser {

	protected static final String ESPACIO = " ";
	protected static final String STRING_VACIO = "";
	protected static final int finCalles = 4;
	
	private Seccion seccion;
	private String nombreOriginal;
	private String[] nombres = new String[5];
	private int errores;
	protected int cantNombres;
	
	public void setSeccion(Seccion seccion) {
		this.seccion = seccion;
	}

	public Seccion getSeccion() {
		return seccion;
	}
	
	public void setNombreOriginal(String localidadOriginal) {
		this.nombreOriginal = localidadOriginal;
	}

	public String getNombreOriginal() {
		return nombreOriginal;
	}
	
	/**
	 * retorna el numero de calles de una expresion tipo string (arg1). 
	 * En arg2 retorna una estructura donde estan contenidas los nombres 
	 * de las calles posibles (calles[1-4]) y la altura (calles[5])
	 * 
	 * @return numero de calles de una expresion 
	 * @throws PException 
	 */
	public int parse() throws PException {
		NombreToken token, tokenant = null;
		int nombreNro,contay,contaesq,contae; //,contruta;
		long cod_calle = 0;
		boolean lleva_nro;
		for(int i = 0; i <= finCalles; i++){
			nombres[i] = STRING_VACIO;
		}

		// estas vars me van a decir si se llego a formar el nro. correcto de calles
		nombreNro = 0; // cant de calles (menos 1)
		contay   = 0; // cant de y'es encontradas
		contaesq = 0; // cant de esquinas encontradas
		contae   = 0; // cant de entre's encontrados
		lleva_nro = false;

		// leemos el primer token de la lista
//		token=scanning(tokenlist,tokentipo);
		NombreScanner scanner = new NombreScanner(getNombreOriginal()); 
		token = scanner.getToken();

		while(token.getTipo() != NombreScanner.FINALIZADO){
			switch(token.getTipo()){
//			choose case tokentipo
				case NombreScanner.CARACTER:	// vemos si el token es un string de caracteres
					lleva_nro = false;
					if (token.getToken().equalsIgnoreCase("Y")){
						// la Y ya la tengo y no me sirve, por lo que la tiro y leo otro token				
//						token = scanning(tokenlist,tokentipo)
						token = scanner.getToken();
						if(".".equals(token.getToken())){ 
							if(tokenant != null){
								// si era un punto la Y era de una abreviatura, por lo que la debo agregar
								// como un token ya procesado (tokenant) y pegar el procesado
								if(STRING_VACIO.equals(nombres[nombreNro]))
									nombres[nombreNro] = tokenant.getToken();
								else
									nombres[nombreNro] = nombres[nombreNro]+ESPACIO+tokenant.getToken();
								
							}
							// restauro la Y como token para que se agregue a la lista
							token.setToken("Y");
							// anulo tokenant para que no se agregue a la lista
							tokenant = null;
						}else {						
							// meto el token para reprocesarlo
//							tokenlist= token+tokenlist
							scanner.putInFront(token);
							// Anulo el token para que no se procese de nuevo
							token = null;
							contay++;
							// Si hay menos de tres y'es, entonces por las dudas incrementamos 
							// callenro
							switch(contay){
//							choose case contay 
								case 1:
									// debiera agregar el tokenant para no perderlo
									if(nombres[nombreNro - 1].equals(STRING_VACIO))
										nombres[nombreNro - 1] = tokenant.getToken();
									else
										nombres[nombreNro - 1] = nombres[nombreNro - 1] + ESPACIO + tokenant.getToken();
									tokenant = null;
									nombreNro++;
									break;
								case 2:
									if(contaesq > 0){
										errores = -107;
										return errores;
									}else{
										// debiera agregar el tokenant para no perderlo
										if(STRING_VACIO.equals(nombres[nombreNro - 1]))
											nombres[nombreNro - 1] = tokenant.getToken();
										else
											nombres[nombreNro - 1] = nombres[nombreNro - 1] + ESPACIO + tokenant.getToken();
										
										tokenant = null;
										nombreNro++;							
									}
									break;
								case 3:
									// podemos deducir que hay dos calles compuestas
									nombres[nombreNro - 2]=nombres[nombreNro - 2] + " Y " + nombres[nombreNro - 1];
									nombreNro = 2;
									nombres[nombreNro] = STRING_VACIO;
									nombres[nombreNro + 1] = STRING_VACIO;
									tokenant.setToken(tokenant.getToken() + " Y");
									break;
								default:
									errores = -101;
									return errores;
							}
						}
					} 
					break;
			case NombreScanner.NUMERO:
					// guardo el viejo valor del token
					nombres[finCalles] = token.getToken();
					// tomo un token más del script
//					token = scanning(tokenlist,tokentipo)
					token = scanner.getToken();
					// si el nro. era el ultimo de la calle entonces 
					if( token.getTipo() == NombreScanner.FINALIZADO){
						//veo si el tokenant es nulo, y no hay calle[1], 
						//en cuyo caso es el primero de la lista y por ello es calle
						if( tokenant == null && STRING_VACIO.equals(nombres[nombreNro])){
							errores = 0;
							nombres[nombreNro] = nombres[finCalles];
							nombres[finCalles] = STRING_VACIO;
							return nombreNro;
						}else{
							// si la calle1 es nula entonces el nombre de la calle esta contenido en tokenant
							// y tokenaux tiene el nro de la altura 
							if(STRING_VACIO.equals(nombres[nombreNro]))
								nombres[nombreNro] = tokenant.getToken();
							else{
								// hay calle y el nro es la altura
								if(tokenant !=null)
									nombres[nombreNro] = nombres[nombreNro] + ESPACIO + tokenant.getToken();
								
							}
							// si el nombre de la calle era uno que llevaba un nro entonces el nro no es la
							// altura sino parte del nombre de la calle por ello lo pego a calle[nro]
							if(lleva_nro){
								nombres[nombreNro] = nombres[nombreNro]+ESPACIO+nombres[5];
								nombres[finCalles] = STRING_VACIO;
								lleva_nro = false;
							}
							errores = 0;
							tokenant = null;
							// exit
							break; // TODO: ver si esta bien el reemplazo de exit por break
						}
					}else{
					// analizo si el token es uno de los que originan el descarte del resto de la lista
					lleva_nro = false;
					switch(EnumSeparadoresCalle.getValor(token.getToken())){
						case ESC:
						case ED:
						case EDIF:
						case ENT:
						case PB:
						case PA:
						case PU:
						case MONOB:
						case MB:
						case MBK:
						case SECC:
						case SECT:
						case SEC:
						case COL:
						case CAS:
						case DTO:
						case DPTO:
						case NUDO:
							//tratamos igual que si fuera el final de la lista			
							//veo si el tokenant es nulo, y no hay calle[1], 
							//en cuyo caso es el primero de la lista y por ello es calle
							if (tokenant == null && STRING_VACIO.equals(nombres[1])){
								errores = 0; 
								nombres[1] = nombres[finCalles];
								nombres[finCalles] = STRING_VACIO;
								return nombreNro;
							}else{
								// si la calle1 es nula entonces el nombre de la calle esta contenido en tokenant
								// y tokenaux tiene el nro de la altura 
								if(STRING_VACIO.equals(nombres[1]))
									nombres[1] = tokenant.getToken();
								else{
									// hay calle y el nro es la altura
									if(tokenant != null)
										nombres[nombreNro] = nombres[nombreNro] + ESPACIO + tokenant.getToken();
								}
								errores = 0;
								return errores;
							}
						default: 
							// debo volver a poner el token en la lista de tokens para reprocesar
							NombreToken tokenNuevo = new NombreToken(token.getToken() + ESPACIO, token.getTipo());
							scanner.putInFront(tokenNuevo);
							// recupero el token que estaba en calle5
							token.setToken(nombres[finCalles]);
							nombres[finCalles] = STRING_VACIO; 
					}					
				}
				break;
			case NombreScanner.ESPECIAL:	// vemos si es caracter especial
					lleva_nro = false;
					switch(EnumSeparadoresCalle.getValor(token.getToken())){
						case BARRA:		// entre,y,S/N,S/C
							if(tokenant == null){ 
								errores=-100;
								return errores;
							}
							switch(EnumSeparadoresCalle.getValor(tokenant.getToken())){
								case ENTRE:
									if( STRING_VACIO.equals(nombres[1])){
										errores = -109;
										return errores;
									}
									if( contay<=1 && contae==0 && contaesq==0){
										// si se da todas esta condiciones tenemos definida un ENTRE para dos calles
										contae++;
										if(contay == 1){
											nombres[nombreNro - 1]=nombres[nombreNro - 1]+" Y "+nombres[nombreNro];
											nombres[nombreNro] = STRING_VACIO;
											nombreNro --;
											contay = 0;
										}
										nombreNro++;
										tokenant = null;
										token = null;
									}else{
										errores = -102;
										return errores;
									}		
								case S: // sin numero o sin calle
//									token = scanning(tokenlist,tokentipo);
									token = scanner.getToken();
									switch(EnumSeparadoresCalle.getValor(token.getToken())){
										// sin numero entonces no se sigue evaluando
										case N:
										case NRO:
										case KM:
											token.setTipo(NombreScanner.FINALIZADO);
											if ( nombreNro == 2 && contay == 1 ){
												nombres[nombreNro - 1] = nombres[nombreNro - 1] + " Y " + nombres[nombreNro];
												nombres[nombreNro] = null;
												contay = 0;
												nombreNro --;
											}
											tokenant = null;
//											TODO: antes era Exit.
										// antes de devolver sin calle, se analiza si existia una
										case C:
											token.setTipo(NombreScanner.FINALIZADO);
											tokenant = null;
											if( nombres[1].length()== 0){
												nombreNro = 0;
//												return ; //TODO: antes era Exit
											}else{
												errores = -103;
												return errores;
											}	
									}		
								default: // separador y
									// tengo otra calle
//									tokenlist = tokenant + " Y " + tokenlist;
									scanner.putInFront(new NombreToken(tokenant.getToken() + " Y ", token.getTipo()));
									tokenant = null;
									token = null;
							}
							case PUNTO:	
								// vemos si no es una abreviatura de esquina
								// choose case tokenant = NRO, N
								switch(EnumSeparadoresCalle.getValor(tokenant.getToken())){
									case NRO:
									case KM:
										// Puede ser la abreviatura de NUMERO
										// en el caso de ser numero, no se reemplaza pero indicaria
										// que la cantidad de calles es uno, en el caso de haber 2 revisar concepto
										tokenant = null;
										token = null;
//										calle[5] = scanning(tokenlist,tokentipo);
										NombreToken nToken = scanner.getToken(); 
										nombres[5]= nToken.getToken();
										if (nToken.getTipo() == 2){
											tokenant = null;
											errores = 0;
											// TODO: era exit;
										}
										else{
											errores = -104; // se esperaba un numero para la altura
											return errores;
										}
									case ESQ:
										// si ya habia una esquina o se definio un entre o habia dos posibles calles
										if(STRING_VACIO.equals(nombres[1])){
											errores = -108;
											return errores;
										}
										if(contaesq > 0 || contae > 0 || contay > 1){
											errores = -105;	// no se contruyo bien la definicion de domicilio
											return errores;
										}
										else{
											// si no debo analizar que pasa cuando tengo una esquina
											if(contay == 1){
												nombres[nombreNro - 1]= nombres[nombreNro - 1] + " Y " + nombres[nombreNro];
												nombres[nombreNro] = STRING_VACIO;
												nombreNro --;
												contay = 0;
											}
											contaesq++;
											nombreNro++;
											tokenant = null;
											token = null;
										}
									case ESC:
									case ED: 
									case EDIF:
									case ENT:
									case PB: 
									case PA:
									case PU:
									case MONOB:
									case MB:
									case MBK:
									case SECC:
									case SECT:
									case SEC:
									case COL:
									case CAS:
									case DTO:
									case DPTO:
									case NUDO:
											tokenant = null;
											token = null;
											scanner.clear();
											errores = 0;
											return nombreNro;
									default:
										//abreviatura
										if (tokenant.getToken().length() > 1){								
											// Entonces  quizas se trate de un abreviatura reemplazable
											// por lo tanto tiro el token '.' y tomo el siguiente 
											token = AbreviaturaManager.getInstance().getReemplazos(tokenant.getToken());
											// veo si debe llevar un nro. Si lo debe llevar analizo si lo tiene
											if( lleva_nro ){
												// leo en tokenant el siguiente nro
//												tokenant = scanning(tokenlist,tokentipo);
												tokenant = scanner.getToken();
												lleva_nro = (tokenant.getTipo() == 2 );
												if(tokenant != null)
//													tokenlist = tokenant + " "+tokenlist;
													scanner.putInFront(new NombreToken(tokenant.getToken() +  ESPACIO, tokenant.getTipo()));
												token.setTipo(3);
											}
											tokenant = null;

										}
										else
											// la abreviatura no es reemplazable.
											// Leemos otro token y perdemos el punto
//											token = scanning(tokenlist,tokentipo);
											token = scanner.getToken();
								}
							case CERITO: // nro.,arroyo,barrio,1°,2°,3°
								switch(EnumSeparadoresCalle.getValor(tokenant.getToken())){
									case UNO:
										token.setToken("PRIMERO");
										break;
									case DOS:
										token.setToken("SEGUNDO");
										break;
									case A:	// en el caso de ser arroyo, abreviatura reemplazable
										token.setToken("ARROYO");
										break;
									case B:	// en el caso de ser barrio, abreviatura reemplazable
										token.setToken("BARRIO");
										break;
									case N:	
										// en el caso de ser numero, no se reemplaza pero indicaria
										// que la cantidad de calles es uno, en el caso de haber 2 revisar concepto
//										calle[5] = scanning(tokenlist,tokentipo);
										NombreToken nToken = scanner.getToken(); 
										nombres[5]= nToken.getToken();
										if( nToken.getTipo() == 2){
											tokenant = null;
											// TODO: era exit;
										}else
											throw new CalleSinAlturaException("Se esperaba un numero para la altura");
										break;
								}
								tokenant = null;
							case GUION:	// numero
									// en el caso de ser numero, no se reemplaza pero indicaria
									// que la cantidad de calles es uno, en el caso de haber 2 revisar concepto
//									calle[5] = scanning(tokenlist,tokentipo);
									NombreToken nToken = scanner.getToken(); 
									nombres[5]= nToken.getToken();
									if( nToken.getTipo() == 2){	// si es un nro
										//veo si el tokenant es nulo, y no hay calle[1], 
										//en cuyo caso es un error
										if (tokenant == null && STRING_VACIO.equals(nombres[1])){
											errores = -106; // Se definio el nro de una calle que no existe
											return nombreNro;
										}
										else{
											// si la calle1 es nula entonces el nombre de la calle esta contenido en tokenant
											// y calle5 tiene el nro de la altura 
											if(STRING_VACIO.equals(nombres[1]))
												nombres[1] = tokenant.getToken();
											else
												// hay calle y el nro es la altura
												nombres[nombreNro] = nombres[nombreNro] + "  " + tokenant.getToken();
											
											tokenant = null;
											// TODO: era exit
										}
									}else{
										errores = -104; // se esperaba un numero para la altura
										return errores;
									}
				}
			}
			if(tokenant != null){
				if(STRING_VACIO.equals(nombres[nombreNro]))
					nombres[nombreNro] = tokenant.getToken();
				else
					nombres[nombreNro] = nombres[nombreNro] + ESPACIO + tokenant.getToken();
			}		
			tokenant = token;
			token = scanner.getToken();

		}
		cantNombres = nombreNro + 1;
		// si hay algo ya procesado en tokenant lo concateno a la calle
		if(tokenant.getTipo()  != NombreScanner.FINALIZADO){
			if( STRING_VACIO.equals(nombres[nombreNro]))
				nombres[nombreNro] = tokenant.getToken();
			else
				nombres[nombreNro] = nombres[nombreNro] + ESPACIO + tokenant.getToken();
			
		}
		if(errores < 0)
			return errores; 
		// Ahora comenzamos el analisis de los casos en los cuales habia separadores
		if(cantNombres == 2 && contay == 1 && !STRING_VACIO.equals(nombres[finCalles])){
			nombres[0] = nombres[0] +" Y " +nombres[1].trim();
			nombres[1] = null;
			nombreNro = 0;
		}
		if( contaesq == 1 && contay == 1){
			nombres[nombreNro - 1] = nombres[nombreNro - 1] + " Y " + nombres[nombreNro];
			nombres[nombreNro] = null;
			nombreNro = 2;
		}	
		// analisis extremos, casos de calles compuestas acompañadas de otra simple
		if (contay == 2){ 
			tokenant.setToken(nombres[contae + 1] + " Y " + nombres[contae + 2]);
			if(validarExistencia(tokenant.getToken(),cod_calle))
				nombres[contae + 1] = tokenant.getToken();
			else{
				nombres[contae] = nombres[contae] + " Y " + nombres[contae + 1];
				nombres[contae + 1] = nombres[contae + 2];
			}
			nombres[contae + 2] = null;
			nombreNro = contae + 2;
		}
		if(nombres[0].isEmpty()){
//			throw new CalleMalFormadaException("No se definio ninguna calle correctamente formada");
			return 0;
		}
		if( !STRING_VACIO.equals(nombres[finCalles]))
			return 4; // Este codigo dice que hay calle CON altura
		return cantNombres;
	}

	protected abstract boolean validarExistencia(String token, long cod_calle) throws PException;

	public String[] getNombresHallados() {
		return nombres;
	}
}
